package mrusanov.view

import xml.{XML, NodeSeq, Node}
import mrusanov.images.ImageInfo
import java.io.File

object PlayerSpriteInfoLoader {
  private val ATTACK_OFFSET_5 = Vector((0, 5), (0, 10), (0, 15), (0, 10), (0, 5))
  private val ATTACK_OFFSET_6 = Vector((0, 5), (0, 10), (0, 15), (0, 15), (0, 10), (0, 5))
  private val ATTACK_OFFSET_7 = Vector((0, 4), (0, 8),(0, 12), (0, 15), (0, 11), (0,8), (0, 4))
  private val ATTACK_OFFSET_8 = Vector((0, 4), (0, 8),(0, 12), (0, 15), (0, 15), (0, 11), (0,8), (0, 4))
  private val ATTACK_OFFSET_9 = Vector((0, 3), (0, 6),(0, 9),(0, 12), (0, 15), (0, 12), (0,9), (0, 6),(0,3))

  private val ATTACK_OFFSETS = Map(5 -> ATTACK_OFFSET_5, 6 -> ATTACK_OFFSET_6, 7 -> ATTACK_OFFSET_7,
    8 -> ATTACK_OFFSET_8, 9 -> ATTACK_OFFSET_9)

  private val DEATH_ALPHA = Vector(0.8f, 0.6f, 0.4f, 0.2f)

  private val IMAGES_PATH = "/data/images/units/"

  lazy val info:Map[String, PlayerSpriteInfo] = loadInfo

  def loadInfo:Map[String, PlayerSpriteInfo] = {
    val path = getClass.getResource("/conf/playerSprites.xml").toURI
    val xml = XML.loadFile(new File(path))

    var retMap:Map[String, PlayerSpriteInfo] = Map()

    (xml \ "sprite") foreach( node => {
      val parsedSprite = parsePlayerSprite(node)
      retMap += parsedSprite.name -> parsedSprite
    })

    retMap
  }


  private def parsePlayerSprite(node:Node):PlayerSpriteInfo = {
    val name = (node \ "@name").toString()
    val soundConfigName = (node \ "@soundConfig").toString()
    require(!soundConfigName.isEmpty, "Sound configuration for sprite " + name + " is empty!")

    val effect = parseGraphicEffect(node)

    val folder = IMAGES_PATH + name

    val standSprite = parsePlayerState(folder, node, Stand, Vector(), effect)
    val attackSprite = parsePlayerState(folder, node, DefaultAttack, standSprite, effect)
    val attackDownSprite = parsePlayerState(folder, node, AttackDown, attackSprite, effect)
    val attackUpSprite = parsePlayerState(folder, node, AttackUp, attackSprite, effect)
    val deathSprite = parsePlayerState(folder, node, Death, standSprite, effect)
    val idleSprite = parsePlayerState(folder, node, Idle, standSprite, effect)
    val defenceSprite = parsePlayerState(folder, node, Defence, standSprite, effect)

    val map = Map(Stand -> standSprite,AttackDown -> attackDownSprite,
      AttackUp -> attackUpSprite, Death -> deathSprite,
      Idle -> idleSprite, Defence -> defenceSprite)
    new PlayerSpriteInfo(name, map, soundConfigName, effect)
  }

  private def parsePlayerState(folder:String, node:Node, state:PlayerViewState, defaultImages:Vector[ImageInfo], effect:Option[GraphicEffectInfoConfig]):Vector[ImageInfo] = {
    var images:Vector[ImageInfo] = Vector()
    (node \ state.name \ "image").foreach(im => {
      images :+= parseImage(folder, im)
    })

    if (images.isEmpty) {
      images = defaultImages
    }

    images = state match {
      case AttackUp => createAttackImages(images, effect, up=true)
      case AttackDown => createAttackImages(images, effect, up = false)
      case Death => createDeathImages(images)
      case _ => images
    }

    images
  }

  // this method has package protection to be used it test code.
  // When it is defined how this method should work, it should
  // be turned to usual private
  def createDeathImages(deathImages:Vector[ImageInfo]):Vector[ImageInfo] = {
    val addedWithoutAlpha = Vector.fill(DEATH_ALPHA.size)(deathImages.last)
    val addedWithAlpha =  addedWithoutAlpha zip DEATH_ALPHA map(ii => ii._1.cloneWithAlpha(ii._2))
    deathImages ++ addedWithAlpha
  }

  // this method has package protection to be used it test code.
  // When it is defined how this method should work, it should
  // be turned to usual private
   def createAttackImages(defaultImages:Vector[ImageInfo],  effect:Option[GraphicEffectInfoConfig], up:Boolean):Vector[ImageInfo] = {
    effect match {
      case Some(_) => defaultImages
      case None => addOffsetToImages(multiplyImages(defaultImages), up)
    }
   }

  private def addOffsetToImages(images:Vector[ImageInfo], up:Boolean):Vector[ImageInfo] = {
    val offset = ATTACK_OFFSETS(images.size)

    images zip offset map {imo => up match {
        case true => imo._1.cloneWithOffset(imo._2._1, -imo._2._2)
        case false => imo._1.cloneWithOffset(imo._2._1, imo._2._2)
      }
    }
  }

  private def multiplyImages(images:Vector[ImageInfo]):Vector[ImageInfo] = {
    assert(images.size != 0, "Images list empty")

    images.size match {
      case 1 => Vector.fill(5)(images(0))
      case 2 => Vector(images(0), images(1), images(1), images(1), images(0))
      case 3 => Vector(images(0), images(1), images(2), images(1), images(0))
      case 4 => Vector(images(0), images(0), images(1), images(2), images(3))
      case _ => images
    }
  }

  private def parseImage(folder:String, node:Node):ImageInfo = {
    val path = (node \ "@path").toString()
    val x = getAttributeOrElse(node \ "@x", 0)
    val y = getAttributeOrElse(node \ "@y", 0)
    val alpha = getAttributeOrElse(node \ "@alpha", 1.0f)
    new ImageInfo(path, folder, x, y, alpha)
  }

  private def getAttributeOrElse(seq:NodeSeq, default:Int):Int = {
    if (seq.isEmpty) {
      default
    } else {
      seq.toString().toInt
    }
  }

  private def getAttributeOrElse(seq:NodeSeq, default:Float):Float = {
    if (seq.isEmpty) {
      default
    } else {
      seq.toString().toFloat
    }
  }

  private def parseGraphicEffect(node:Node):Option[GraphicEffectInfoConfig] = {
    val effect = (node \ "effect" \ "@name")
    if (effect.isEmpty) {
      None
    } else {
      val indexElement = (node \ "effect" \ "@index")
      val index = if (indexElement.isEmpty) 0
                  else indexElement.toString().toInt
      Some(new GraphicEffectInfoConfig(effect.toString(), index))
    }
  }
}
